﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Beacon.SPC
{
    /// <summary>
    /// Statistical Process Control
    /// based on Shewhart 
    /// </summary>
    public class SpcControlLimit
    {
        #region Member
        private SpcChartType _chartType = SpcChartType.XBarR;
        private SpcDataType _dataType = SpcDataType.Mean;
        private double _mean = double.NaN;
        private double _sigma = double.NaN;
        private double _sampleSize = double.NaN;

        // target line
        public double TARGET { get; set; }
        //  upper control limit line
        public double UCL { get; set; }
        // lower control limit line
        public double LCL { get; set; }
        // upper warning limit line
        public double UWL { get; set; }
        // lower warning limit line
        public double LWL { get; set; }
        // upper one-sigma limit line
        public double UOL { get; set; }
        // lower one-sigma limit line
        public double LOL { get; set; }

        public double Mean { get { return _mean; } set { _mean = value; } }
        public double Sigma { get { return _sigma; } set { _sigma = value; } }

        // if all limits line been calculated.
        public bool Calculated { get; set; }
        
        #endregion

        #region Constructor
        
        /// <summary>
        /// For trained data (already have mean and sigma value)
        /// </summary>
        /// <param name="cType"></param>
        /// <param name="dType"></param>
        /// <param name="mean"></param>
        /// <param name="sigma"></param>
        /// <param name="sampleSize"></param>
        public SpcControlLimit(SpcChartType cType, SpcDataType dType, double mean, double sigma, double sampleSize)
        {
            _chartType = cType;
            _dataType = dType;
            _mean = mean;
            _sigma = sigma;
            _sampleSize = sampleSize;
            Calculate();
        }

        /// <summary>
        /// For training data
        /// </summary>
        /// <param name="cType"></param>
        /// <param name="dType"></param>
        /// <param name="data"></param>
        /// <param name="sampleSize"></param>
        public SpcControlLimit(SpcChartType cType, SpcDataType dType, List<SpcData> data, double sampleSize, ScalarInspection inspection)
        {
            _chartType = cType;
            _dataType = dType;
            _sampleSize = sampleSize;
            Calculate(data, inspection);
        }

        #endregion

        #region Public member functions

        private void Calculate(List<SpcData> data, ScalarInspection inspection)
        {
            double xbarMean  = 0;
            double rangeMean = 0;
            double stdevMean = 0;
            int count = 0;

            switch (_chartType)
            {
                case SpcChartType.XBarR:
                    
                    foreach (SpcData d in data)
                    {
                        xbarMean += d.XBar;
                        rangeMean += d.Range;
                    }
                    xbarMean /= data.Count;
                    rangeMean /= data.Count;
                    _sigma = SpcFormula.Sigma(_chartType, xbarMean, rangeMean, _sampleSize);

                    if (_dataType == SpcDataType.Mean)
                    {
                        _mean = xbarMean;
                        SetMean(_mean, _sigma, false);
                    }
                    else if (_dataType == SpcDataType.Range)
                    {
                        _mean = rangeMean;
                        SetRange(_mean, (int)_sampleSize);
                    }
                    else
                    {
                        throw new SpcException("spc data type not support");
                    }
                    break;

                case SpcChartType.XBarS:
                    
                    foreach (SpcData d in data)
                    {
                        xbarMean += d.XBar;
                        stdevMean += d.StDev;
                    }
                    xbarMean /= data.Count;
                    stdevMean /= data.Count;
                    _sigma = SpcFormula.Sigma(_chartType, xbarMean, stdevMean, _sampleSize);

                    if (_dataType == SpcDataType.Mean)
                    {
                        _mean = xbarMean;
                        SetMean(_mean, _sigma, false);
                    }
                    else if (_dataType == SpcDataType.Std)
                    {
                        _mean = stdevMean;
                        SetStd(_mean, (int)_sampleSize);
                    }
                    else
                    {
                        throw new SpcException("spc data type not support");
                    }
                    break;

                case SpcChartType.XMR:

                    foreach (SpcData d in data)
                    {
                        xbarMean += d.X0;
                        rangeMean += d.MR;
                    }
                    xbarMean /= data.Count;
                    rangeMean /= data.Count;
                    _sigma = SpcFormula.Sigma(_chartType, xbarMean, rangeMean, _sampleSize);

                    if (_dataType == SpcDataType.Mean)
                    {
                        _mean = xbarMean;
                        SetMean(_mean, _sigma, false);
                    }
                    else if (_dataType == SpcDataType.Range)
                    {
                        _mean = rangeMean;
                        SetRange(_mean, 2);
                    }
                    else
                    {
                        throw new SpcException("spc data type not support");
                    }
                    break;

                case SpcChartType.C:
                    /*
                     * CL = C-bar = ∑Ci/N
                     * UCL = C-bar + 3* Sqrt(C-bar)
                     * Ci = number of noncomforming items
                     */
                    foreach (SpcData d in data)
                    {
                        xbarMean += d.C(inspection);
                    }
                    xbarMean /= data.Count;
                    _sigma = SpcFormula.Sigma(_chartType, xbarMean, rangeMean, _sampleSize);

                    if (_dataType == SpcDataType.Mean)
                    {
                        _mean = xbarMean;
                        SetMean(_mean, _sigma, true);
                    }
                    else
                    {
                        throw new SpcException("spc data type not support");
                    }
                    break;

                case SpcChartType.U:
                    /*
                     *  CL  = U-bar = ∑Ui/∑Ni
                     *  Ui  = Ci/Ni
                     *  UCL = U-bar + 3* Sqrt(U-bar/Ni)
                     */
                    foreach (SpcData d in data)
                    {
                        xbarMean += d.U(inspection);
                        count += d.Count();
                    }
                    _mean = xbarMean/count;
                    _sampleSize = count / data.Count;
                    _sigma = SpcFormula.Sigma(_chartType, _mean, rangeMean, _sampleSize);

                    if (_dataType == SpcDataType.Mean)
                    {
                        SetU(_mean, _sigma, (int)_sampleSize, (int)_sampleSize);
                    }
                    else
                    {
                        throw new SpcException("spc data type not support");
                    }
                    break;

                case SpcChartType.P:
                    /*
                     *  CL  = P-bar = ∑Pi/ ∑Ni
                     *  UCL = P-bar + 3*Sqrt( (P-bar*(1-P-bar))/Ni )
                     *  Pi = number of noncomforming items
                     */
                    foreach (SpcData d in data)
                    {
                        xbarMean += d.P(inspection);
                        count += d.Count();
                    }
                    _mean = xbarMean / count;
                    _sampleSize = count / data.Count;
                    _sigma = SpcFormula.Sigma(_chartType, _mean, rangeMean, _sampleSize);

                    if (_dataType == SpcDataType.Mean)
                    {
                        SetP(_mean, _sigma, (int)_sampleSize, (int)_sampleSize);
                    }
                    else
                    {
                        throw new SpcException("spc data type not support");
                    }
                    break;
                case SpcChartType.NP:

                    foreach (SpcData d in data)
                    {
                        xbarMean += d.NP(inspection);
                    }
                    _mean = xbarMean/_sampleSize;
                    _sigma = SpcFormula.Sigma(_chartType, _mean, rangeMean, _sampleSize);

                    if (_dataType == SpcDataType.Mean)
                    {
                        SetMean(_mean, _sigma, true);
                    }
                    else
                    {
                        throw new SpcException("spc data type not support");
                    }
                    break;
            }
            Calculated = true;            
        }

        /// <summary>
        /// calculate by given chart type, mean, sigma and sample size.
        /// throw 
        /// </summary>
        /// <param name="mean"></param>
        /// <param name="sigma"></param>
        /// <param name="sampleSize"></param>
        public void Calculate(int sampleSizeInstance)
        {
            switch (_chartType)
            {
                case SpcChartType.XBarR:
                    if (_dataType == SpcDataType.Mean)
                    {
                        SetMean(_mean, _sigma, false);
                    }
                    else if (_dataType == SpcDataType.Range)
                    {
                        SetRange(_mean, (int)_sampleSize);
                    }
                    else
                    {
                        throw new SpcException("spc data type not support");
                    }
                    break;
                case SpcChartType.XBarS:
                    if (_dataType == SpcDataType.Mean)
                    {
                        SetMean(_mean, _sigma, false);
                    }
                    else if (_dataType == SpcDataType.Std)
                    {
                        SetStd(_mean, (int)_sampleSize);
                    }
                    else
                    {
                        throw new SpcException("spc data type not support");
                    }
                    break;
                case SpcChartType.XMR:
                    if (_dataType == SpcDataType.Mean)
                    {
                        SetMean(_mean, _sigma, false);
                    }
                    else if (_dataType == SpcDataType.Range)
                    {
                        // MR chart sample size must be 2
                        SetRange(_mean, 2);
                    }
                    else
                    {
                        throw new SpcException("spc data type not support");
                    }
                    break;
                case SpcChartType.C:
                    if (_dataType == SpcDataType.Mean)
                    {
                        SetMean(_mean, _sigma, true);
                    }
                    else
                    {
                        throw new SpcException("spc data type not support");
                    }
                    break;
                case SpcChartType.U:
                    if (_dataType == SpcDataType.Mean)
                    {
                        SetU(_mean, _sigma, (int)_sampleSize, sampleSizeInstance);
                    }
                    else
                    {
                        throw new SpcException("spc data type not support");
                    }
                    break;
                case SpcChartType.P:
                    if (_dataType == SpcDataType.Mean)
                    {
                        SetP(_mean, _sigma, (int)_sampleSize, sampleSizeInstance);
                    }
                    else
                    {
                        throw new SpcException("spc data type not support");
                    }
                    break;
                case SpcChartType.NP:
                    if (_dataType == SpcDataType.Mean)
                    {
                        SetMean(_mean, _sigma, true);
                    }
                    else
                    {
                        throw new SpcException("spc data type not support");
                    }
                    break;
            }
            Calculated = true;
        }

        /// <summary>
        /// default calaulate assume that sample size is fixed.
        /// </summary>
        public void Calculate()
        {
            Calculate((int)_sampleSize);
        }

        #endregion

        #region Private member functions
        private void SetMean(double mean, double sigma, bool zeroBase)
        {
            TARGET = mean;
            UCL = mean + 3 * sigma;
            UWL = mean + 2 * sigma;
            UOL = mean + 1 * sigma;
            
            if (zeroBase)
            {
                LCL = Math.Max(mean - 3 * sigma, 0);
                LWL = Math.Max(mean - 2 * sigma, 0);
                LOL = Math.Max(mean - 1 * sigma, 0);
            }
            else
            {
                LCL = mean - 3 * sigma;
                LWL = mean - 2 * sigma;
                LOL = mean - 1 * sigma;
            }
        }
        private void SetStd(double stdbar, int sampleSize)
        {
            if (sampleSize <= 0)
                UCL = UWL= LWL = LCL = double.NaN;
            if (sampleSize <= 25)
            {
                UCL = stdbar * SpcConstant.GetBs001(sampleSize);
                UWL = stdbar * SpcConstant.GetBs025(sampleSize);
                LCL = stdbar * SpcConstant.GetBs999(sampleSize);
                LWL = stdbar * SpcConstant.GetBs975(sampleSize);
            }
            else
            {
                UCL = stdbar * SpcConstant.GetB4s(sampleSize);
                UWL = double.NaN;
                LCL = stdbar * SpcConstant.GetB3s(sampleSize);
                LWL = double.NaN;
            }
        }
        private void SetRange(double rangeBar, int sampleSize)
        {
            if (sampleSize <= 0)
                UCL = UWL = LWL = LCL = double.NaN;

            UCL = rangeBar * SpcConstant.GetDr001(sampleSize);
            UWL = rangeBar * SpcConstant.GetDr025(sampleSize);
            LCL = rangeBar * SpcConstant.GetDr999(sampleSize);
            LWL = rangeBar * SpcConstant.GetDr975(sampleSize);
        }
        private void SetU(double uBar, double sigma, int sampleSize, int sampleSizeInstance)
        {
            double minSize = 0.75 * sampleSize;
            double maxSize = 1.25 * sampleSize;
            double tmpSigma = sigma;
            if (sampleSizeInstance > maxSize || sampleSizeInstance < minSize)
                tmpSigma = Math.Sqrt(uBar / sampleSizeInstance);

            UCL = uBar + 3 * tmpSigma;
            UWL = uBar + 2 * tmpSigma;
            LWL = Math.Max(uBar - 2 * tmpSigma,0);
            LCL = Math.Max(uBar - 3 * tmpSigma,0); 
        }
        private void SetP(double pBar, double sigma, int sampleSize, int sampleSizeInstance)
        {
            double minSize = 0.75 * sampleSize;
            double maxSize = 1.25 * sampleSize;
            double tmpSigma = sigma;
            if (sampleSizeInstance > maxSize || sampleSizeInstance < minSize)
                tmpSigma = Math.Sqrt(pBar * (1 - pBar) / sampleSizeInstance);

            UCL = pBar + 3 * tmpSigma;
            UWL = pBar + 2 * tmpSigma;
            LWL = Math.Max(pBar - 2 * tmpSigma, 0);
            LCL = Math.Max(pBar - 3 * tmpSigma, 0);
        }
        #endregion

        #region Utils
        public override string ToString()
        {
            if (Calculated)
                return string.Format("chart: {0}  type: {1} target : {2} lcl:{3} lwl: {4} lol: {5} uol: {6} uwl: {7} ucl: {8}.",
                                    _chartType.ToString(), 
                                    _dataType.ToString(), 
                                    TARGET, 
                                    LCL, 
                                    LWL, 
                                    LOL, 
                                    UOL, 
                                    UWL, 
                                    UCL);
            else
                return "not calculated";
        }
        #endregion
    }

}
